package org.exhaust.framework.net.server.tcp;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.PooledByteBufAllocator;
import io.netty.channel.*;
import io.netty.channel.socket.SocketChannel;
import io.netty.util.NetUtil;
import lombok.extern.slf4j.Slf4j;
import org.exhaust.framework.net.common.protocol.ChannelEventDispatcher;
import org.exhaust.framework.net.common.protocol.EventLoopFactory;
import org.exhaust.framework.net.common.protocol.RunningState;
import org.exhaust.framework.net.common.protocol.decoder.tcp.NoneSharableTcpByteBufDecoder;
import org.exhaust.framework.net.common.protocol.encoder.TcpFrameToByteBufEncoder;
import org.exhaust.framework.net.common.protocol.handler.ConnectionMonitorHandler;
import org.exhaust.framework.net.common.protocol.handler.idle.ChannelReaderIdleEventHandler;
import org.exhaust.framework.net.common.protocol.handler.idle.ChannelReaderIdleStateHandler;
import org.exhaust.framework.net.common.protocol.handler.idle.ChannelWriterIdleEventHandler;
import org.exhaust.framework.net.common.protocol.handler.idle.ChannelWriterIdleStateHandler;

import java.util.function.Supplier;

@Slf4j
public class BaseTcpServer implements TcpServer {

    private volatile RunningState state = RunningState.TERMINATED;

    private volatile Channel serverSocketChannel = null;

    private ServerBootstrap serverBootstrap;

    private EventLoopGroup bossEventLoopGroup;

    private EventLoopGroup workerEventLoopGroup;

    private TcpServerProperty serverProperty;

    private Supplier<NoneSharableTcpByteBufDecoder> decoderSupplier;

    private ChannelEventDispatcher eventDispatcher;

    private TcpFrameToByteBufEncoder encoder;

    private ConnectionMonitorHandler connectionMonitorHandler = null;

    /**
     * @param decoderSupplier supply中必须每次都new一个新对象，因为ByteToMessageDecoder不是线程安全的
     * @param encoder
     * @param eventDispatcher
     * @param serverProperty
     */
    public BaseTcpServer(Supplier<NoneSharableTcpByteBufDecoder> decoderSupplier, TcpFrameToByteBufEncoder encoder, ChannelEventDispatcher eventDispatcher, TcpServerProperty serverProperty) {
        this.decoderSupplier = decoderSupplier;
        this.encoder = encoder;
        this.eventDispatcher = eventDispatcher;
        this.serverProperty = serverProperty;
        this.serverBootstrap = new ServerBootstrap();
        int workerThreadNum = serverProperty.getWorkerThreadNum() == 0 ? Runtime.getRuntime().availableProcessors() * 2 : serverProperty.getWorkerThreadNum();
        this.bossEventLoopGroup = EventLoopFactory.eventLoopGroup(1, "TCP-Server-Boss-EventLoopGroup");
        this.workerEventLoopGroup = EventLoopFactory.eventLoopGroup(workerThreadNum, "TCP-Server-Worker-EventLoopGroup");
        this.connectionMonitorHandler = new ConnectionMonitorHandler(eventDispatcher);
    }

    @Override
    public void start() throws Exception {
        this.state = RunningState.RUNNING;
        serverBootstrap.group(bossEventLoopGroup, workerEventLoopGroup)
                .channel(EventLoopFactory.serverSocketChannelClass())
                .option(ChannelOption.SO_REUSEADDR, Boolean.TRUE)
                .option(ChannelOption.SO_BACKLOG, serverProperty.getBacklog() > 0 ? serverProperty.getBacklog() : NetUtil.SOMAXCONN)
                .childOption(ChannelOption.TCP_NODELAY, Boolean.TRUE)
                .childOption(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT)
                .childHandler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel ch) throws Exception {
                        ChannelPipeline pipeline = ch.pipeline();
                        if (serverProperty.getReaderIdleSeconds() > 0) {
                            pipeline.addLast("ClientReaderIdleStateHandler", new ChannelReaderIdleStateHandler(serverProperty.getReaderIdleSeconds()));
                            pipeline.addLast("ClientReaderIdleEventHandler", new ChannelReaderIdleEventHandler(eventDispatcher));
                        }
                        if (serverProperty.getWriterIdleSeconds() > 0) {
                            pipeline.addLast("ClientWriterIdleStateHandler", new ChannelWriterIdleStateHandler(serverProperty.getWriterIdleSeconds()));
                            pipeline.addLast("ClientWriterIdleEventHandler", new ChannelWriterIdleEventHandler(eventDispatcher));
                        }
                        pipeline.addLast("ConnectionMonitorHandler", connectionMonitorHandler);
                        if (decoderSupplier != null) {
                            final NoneSharableTcpByteBufDecoder decoder = decoderSupplier.get();
                            if (decoder != null) {
                                pipeline.addLast("FrameDecoderAndHandler", decoder);
                            }
                        }
                        if (encoder != null) {
                            pipeline.addLast("FrameEncoder", encoder);
                        }

                    }
                });
        ChannelFuture f = serverBootstrap.bind(serverProperty.getLocalAddress(), serverProperty.getPort()).sync();
        this.serverSocketChannel = f.channel();
        log.info("启动TcpServer成功");
    }

    @Override
    public void stopGracefully() {
        this.state = RunningState.STOPPING;
        if (serverSocketChannel != null) {
            try {
                serverSocketChannel.close();
            } catch (Throwable e) {
                log.warn("close server socket channel failed:{}", e.getMessage());
            }
        }
        this.serverSocketChannel = null;
        this.workerEventLoopGroup.shutdownGracefully();
        this.bossEventLoopGroup.shutdownGracefully();
        this.state = RunningState.TERMINATED;
    }

    @Override
    public Channel getServerSocketChannel() {
        return serverSocketChannel;
    }

    @Override
    public RunningState getState() {
        return state;
    }

    @Override
    public EventLoopGroup bossEventLoopGroup() {
        return this.bossEventLoopGroup;
    }

    @Override
    public EventLoopGroup workerEventLoopGroup() {
        return this.workerEventLoopGroup;
    }
}
